package cn.openea.perpartner.meily.web.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.openea.perpartner.common.core.constant.DateConstant;
import cn.openea.perpartner.common.core.exception.ServiceException;
import cn.openea.perpartner.common.core.utils.DateUtils;
import cn.openea.perpartner.common.core.utils.StringUtils;
import cn.openea.perpartner.meily.constant.enums.NotifySceneEnum;
import cn.openea.perpartner.meily.constant.enums.NotifySendEnum;
import cn.openea.perpartner.meily.thirdparty.UserSystemApi;
import cn.openea.perpartner.meily.thirdparty.notify.NotifySendEvent;
import cn.openea.perpartner.meily.web.entity.RevenueRecordsArc;
import cn.openea.perpartner.meily.web.entity.vo.RevenueRecordsSearchVo;
import cn.openea.perpartner.meily.web.mapper.RevenueRecordsArcMapper;
import cn.openea.perpartner.meily.web.service.IRevenueRecordsArcService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service("revenueRecordsArcService")
public class RevenueRecordsArcServiceImpl extends ServiceImpl<RevenueRecordsArcMapper, RevenueRecordsArc> implements IRevenueRecordsArcService {

    @Value("${payment.order.authSalt}")
    private String authSalt;
    @Value("${payment.order.getTokenUrl}")
    private String getOrderSysTokenUrl;
    @Value("${payment.order.subPayToWallet}")
    private String subPayToWallet;
    @Value("${integral_sysytem_id}")
    private String INTEGRAL_SYSTEM_ID;

    @Resource
    private UserSystemApi userSystemApi;
    @Resource
    private ApplicationContext applicationContext;


    @Override
    public List<RevenueRecordsArc> listBySearchVo(RevenueRecordsSearchVo searchVo) {
        if (searchVo.getIds() != null && !searchVo.getIds().isEmpty()) {
            return baseMapper.selectBatchIds(searchVo.getIds());
        }
        QueryWrapper<RevenueRecordsArc> wrapper = new QueryWrapper<>();
        wrapper.eq("DATE_FORMAT(settlement_date,'%Y-%m')", searchVo.getTimePeriod().format(DateTimeFormatter.ofPattern(DateConstant.DEFAULT_MONTH_PATTERN)));
        List<RevenueRecordsArc> list = baseMapper.selectList(wrapper);
        // 判断是否关注公众号
        Map<String, String> map = userSystemApi.isBindOfficialAccount(list.stream()
                .map(RevenueRecordsArc::getUserId)
                .collect(Collectors.toList()));
        if (map == null || map.isEmpty()) {
            return list;
        }
        list.forEach(item -> {
            item.setBindOfficialAccount(StringUtils.isNotBlank(map.get(item.getUserId())) ? map.get(item.getUserId()) : "否");
        });
        return list;
    }

    @Override
    public BigDecimal countCumulative(LocalDate timePeriod) {
        if (timePeriod == null) {
            return BigDecimal.ZERO;
        }
        return baseMapper.countCumulative(timePeriod.format(DateTimeFormatter.ofPattern(DateConstant.DEFAULT_MONTH_PATTERN)));
    }

    /**
     * 新增
     *
     * @param revenueRecordsArc 实例对象
     * @return
     */
    @Override
    public boolean add(RevenueRecordsArc revenueRecordsArc) {
        return save(revenueRecordsArc);
    }

    /**
     * 收益发放
     * 步骤：
     * 1.更新信息；
     * 2.发钱到钱包余额；
     * 3.发通知;
     *
     * @param ids （收益记录ids）
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> incomeDistribution(@NonNull List<String> ids) {
        if (ids.isEmpty()) {
            return null;
        }
        List<String> failIds;
        // 判断这些id里面是否有已经结算的数据
        LambdaQueryWrapper<RevenueRecordsArc> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RevenueRecordsArc::getStatus, "已结算")
                .in(RevenueRecordsArc::getId, ids);
        failIds = baseMapper.selectList(queryWrapper)
                .stream()
                .map(RevenueRecordsArc::getId)
                .collect(Collectors.toList());
        failIds.forEach(ids::remove);
        // 剩余的id则是没有结算的数据
        for (String id : ids) {
            String errStr = singleBilling(id);
            if (StringUtils.isNotBlank(errStr)) {
                // 将id保存
                failIds.add(id);
            }
        }
        return failIds;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String singleBilling(String id) {
        String errStr = "";
        try {
            RevenueRecordsArc revenueRecordsArc = getById(id);
            if (revenueRecordsArc == null) {
                errStr = "当前月收益不可发放";
                return errStr;
            }
            if (!"未结算".equals(revenueRecordsArc.getStatus())) {
                errStr = "当前收益已发放，不可重复发放";
                return errStr;
            }
            // 1.更新信息
            LambdaUpdateWrapper<RevenueRecordsArc> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(RevenueRecordsArc::getId, revenueRecordsArc.getId());
            updateWrapper.eq(RevenueRecordsArc::getStatus, "未结算");
            updateWrapper.set(RevenueRecordsArc::getUpdateTime, DateUtil.now());// 更新时间
            updateWrapper.set(RevenueRecordsArc::getTransactionDate, DateUtil.now());// 交易日期
            updateWrapper.set(RevenueRecordsArc::getStatus, "已结算");// 状态。未结算、已结算
            this.update(updateWrapper);
            double _reward = 0.0;// 累计收益
            double points = 0.0;// 需要增加的积分 = 累计收益 * 100.0
            if (StrUtil.isNotBlank(revenueRecordsArc.getAccumulatedIncome())) {
                _reward = Double.parseDouble(revenueRecordsArc.getAccumulatedIncome());
                points = _reward * 100.0;
            }
            // 2.调用积分系统：转入钱包
            JSONObject object1 = new JSONObject();
            object1.put("userId", revenueRecordsArc.getUserId());// 区域服务商id
            object1.put("points", String.format("%.0f", points));// 保留0位小数
            object1.put("reason", revenueRecordsArc.getSettlementDate() + "的平台收益，共" + String.format("%.2f", _reward) + "元");
            object1.put("sceneId", INTEGRAL_SYSTEM_ID);// 自定义积分体系 ID
            object1 = getToken(object1);
            String result = subPayToWallet(object1);
            String code = JSON.parseObject(result).getString("code");
            if (!"200".equals(code)) {
                errStr = "调用积分系统失败";
                // 钱发放失败，回滚这个数据
                throw new ServiceException("调用积分系统失败");
            }
            // 3.发放区域服务商团销收益通知
            HashMap<String, Object> params = new HashMap<>();// 模版ID
            params.put("templateid", "6xk6vCV4UzRk4bX4QpECYmPXZ38DCACPNE-_mKCmtkI");
            params.put("first", "【区域服务商NO." + revenueRecordsArc.getProviderId() + "】亲，请查收" + DateUtils.formatDate(revenueRecordsArc.getSettlementDate(), "yyyy年MM月") + "的平台收益！");
            params.put("keyword1", "NO." + revenueRecordsArc.getProviderId());// 商户名称
            params.put("keyword2", "平台收益清算");// 交易类型
            params.put("keyword3", String.format("%.2f", _reward) + "元");// 实际到账
            params.put("keyword4", DateUtils.formatDate(revenueRecordsArc.getSettlementDate(), "yyyy-MM-dd"));// 结算日期
            params.put("keyword5", DateUtils.formatDate(DateUtil.now(), "yyyy-MM-dd"));// 订单交易日期
            params.put("remark", "点击查看收益详情，感谢您对平台的支持！温馨提示：您所在区域发现不少潜在消费者，建议发起商品销售，赚取更多收益～");
            params.put("pagepath", "/pages/index/incomeIndex");// （链接到【收益页面】）
            params.put("pageurl", "/pages/index/incomeIndex");// （链接到【收益页面】）
            params.put("docUnid", revenueRecordsArc.getId());
            params.put("userId", revenueRecordsArc.getUserId());
            Map<String, Object> data = new HashMap<>();
            data.put(NotifySendEnum.WX_PUBLIC_NUMBER.name(), params);
            try {
                applicationContext.publishEvent(new NotifySendEvent(this,
                        NotifySendEnum.WX_PUBLIC_NUMBER,
                        NotifySceneEnum.GOODS_DELIVERY,
                        data));
            } catch (Exception e) {
                errStr = "给区域服务商发送团销收益通知失败";
                // 这里不抛异常，因为钱已经发放出去了，通知失败可以接受
                return errStr;
            }
        } catch (Exception e) {
            // 这里这个异常是为了回滚数据
            throw new ServiceException(e.getMessage());
        } finally {
            return errStr;
        }
    }


    /** ********************* 以下调用其他系统的 ******************************* */

    /**
     * 获取token与authstr
     *
     * @param
     * @return
     */
    public JSONObject getToken(JSONObject object) {
        HttpResponse resp = HttpRequest.post(getOrderSysTokenUrl)
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .body(object.toJSONString())
                .execute();
        String result = resp.body();

        String token = JSON.parseObject(result).getString("data");
        String tmpStr = authSalt.charAt(2) + authSalt.charAt(8) + token + authSalt.charAt(10) + authSalt.charAt(5);
        object.put("token", token);
        object.put("authStr", SecureUtil.md5(tmpStr));
        return object;
    }

    /**
     * 积分系统：转入钱包
     *
     * @return
     */
    public String subPayToWallet(JSONObject object) {
        HttpResponse resp = HttpRequest.post(subPayToWallet)
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .body(object.toJSONString())
                .execute();
        return resp.body();
    }


}